package dao

import (
	"net/http/httptest"
	"net/http/httputil"
	"strings"
	"sync"

	"gin-gateway/core/init"
	"gin-gateway/core/utils"
	"gin-gateway/core/dto"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"log"
	"gin-gateway/reverse_proxy"
)

// 所有service信息
type ServiceDetail struct {
	Info          *ServiceInfo   `json:"info" description:"基本信息"`
	HTTPRule      *HttpRule      `json:"http_rule" description:"http_rule"`
	TCPRule       *TcpRule       `json:"tcp_rule" description:"tcp_rule"`
	GRPCRule      *GrpcRule      `json:"grpc_rule" description:"grpc_rule"`
	LoadBalance   *LoadBalance   `json:"load_balance" description:"load_balance"`
	AccessControl *AccessControl `json:"access_control" description:"access_control"`
	CircuitConfig *CircuitConfig `json:"circuit_config" description:"circuit_config"`
}

// ServiceManagerHandler 服务管理（http / https / tcp / grpc / ）
var ServiceManagerHandler *ServiceManager

func init() {
	ServiceManagerHandler = NewServiceManager()
}

// ServiceManager 服务管理器
type ServiceManager struct {
	ServiceInfoMap    map[string]*ServiceDetail // （http / https / tcp / grpc / ）
	ServiceDetailList []*ServiceDetail          // （http / https / tcp / grpc / ）
	Locker            sync.RWMutex
	initOnce          sync.Once
	err               error
}

func NewServiceManager() *ServiceManager {
	return &ServiceManager{
		ServiceInfoMap:    map[string]*ServiceDetail{},
		ServiceDetailList: []*ServiceDetail{},
		Locker:            sync.RWMutex{},
		initOnce:          sync.Once{},
		err:               nil,
	}
}

func (s *ServiceManager) GetTcpServiceList() []*ServiceDetail {
	var list []*ServiceDetail
	for _, serverItem := range s.ServiceDetailList {
		tempItem := serverItem
		if tempItem.Info.LoadType == utils.LoadTypeTCP {
			list = append(list, tempItem)
		}
	}
	return list
}

func (s *ServiceManager) GetGrpcServiceList() []*ServiceDetail {
	var list []*ServiceDetail
	for _, serverItem := range s.ServiceDetailList {
		tempItem := serverItem
		if tempItem.Info.LoadType == utils.LoadTypeGRPC {
			list = append(list, tempItem)
		}
	}
	return list
}

// 解析req请求 ，只接受http，是否能匹配数据库内规则,将该匹配服务的所有信息保存至上下文
func (s *ServiceManager) HTTPAccessMode(c *gin.Context) (*ServiceDetail, error) {
	//1、前缀匹配 /abc ==> serviceSlice.rule
	//2、域名匹配 www.test.com ==> serviceSlice.rule
	path := c.Request.URL.Path          // path=/abc/xx
	host := c.Request.Host              // host=www.test.com:8080
	host, _, _ = strings.Cut(host, ":") // 直接分割主机名和端口

	for _, serviceItem := range s.ServiceDetailList {
		if serviceItem.Info.LoadType != utils.LoadTypeHTTP {
			continue
		}
		if serviceItem.HTTPRule.Rule == host && serviceItem.HTTPRule.RuleType == utils.HTTPRuleTypeDomain { // 如果远程ip能匹配
			return serviceItem, nil
		}
		if strings.HasPrefix(path, serviceItem.HTTPRule.Rule) && serviceItem.HTTPRule.RuleType == utils.HTTPRuleTypePrefixURL { // 如果/abc/xx能匹配
			return serviceItem, nil
		}
	}
	return nil, errors.New("not matched service")
}

// LoadOnce 向ServiceManager中填充数据。必须配合单例使用，否则没有效果
func (s *ServiceManager) LoadOnce() error {
	s.initOnce.Do(func() {
		ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) // NewRecorder创建响应记录器
		tx, err := Init.GetGormPool("default_db")
		if err != nil {
			s.err = err
			return
		}

		serviceInfo := &ServiceInfo{}
		params := &dto.ServiceListInput{PageNo: 1, PageSize: 99999}
		ServiceInfoList, _, err := serviceInfo.PageList(ctx, tx, params)
		if err != nil {
			s.err = err
			return
		}

		s.Locker.RLock()
		defer s.Locker.RUnlock()
		for _, v := range ServiceInfoList {
			tmp := v
			serviceDetail, err := tmp.ServiceDetail(ctx, tx, &tmp)
			if err != nil {
				s.err = err
				return
			}
			s.ServiceInfoMap[v.ServiceName] = serviceDetail
			s.ServiceDetailList = append(s.ServiceDetailList, serviceDetail)

			if serviceDetail.Info.LoadType == utils.LoadTypeHTTP {
				lb, err := LoadBalancerManager.GetLoadBalancer(serviceDetail)
				if err != nil {
					log.Fatalln("getReverseProxy get load balancer failed, err:", err)
				}

				// ReverseProxy Transporter 连接池
				Transport, err := TransporterManager.GetTrans(serviceDetail)
				if err != nil {
					log.Fatalln("getReverseProxy get transporter failed, err:", err)
				}

				HttpReverseProxyPool[serviceDetail.Info.ServiceName] = reverse_proxy.NewLoadBalanceReverseProxy(nil, lb, Transport)
			}
		}
	})
	return s.err
}

var HttpReverseProxyPool = make(map[string]*httputil.ReverseProxy, 5)
